<?php
/*
 *      wiki.class.php
 *
 *      Copyright 2010 Patrice Ferlet <metal3d@gmail.com>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

// $Id: wiki.class.php 19 2010-12-14 22:46:23Z Metal3d $
// $HeadURL: http://valaya.googlecode.com/svn/trunk/utils/wiki.class.php $

/**
 *  This is a simple Wiki parser. You only need to give wiki text to
 *  constructor, and use "compile" method to get HTML.
 *  This class require tidy extension !
 * */
class Wiki {

	private $text;
	private $wiki;
	private $working;


	/**
	 * Wiki constructor, get wiki content
	 *
	 * @param string $wiki_text
	 * */
	public function __construct($wiki_text) {
		$this->text = $wiki_text;
		$this->working = $wiki_text;

		$tpl = Template::getMain();
		$scripts = array(
			_url('js/sh/shCore.js'),
			_url('js/sh/shBrushPhp.js'),
			_url('js/sh/shBrushJScript.js'),
		);

		$styles = array(
			_url('styles/sh/shCore.css'),
			_url('styles/sh/shThemeDefault.css'),
		);

		foreach ( $scripts as $s ) {
			$tpl->head .= '<script type="text/javascript" src="'.$s.'"></script>';
		}

		foreach ( $styles as $s ) {
			$tpl->head .= '<link href="'.$s.'" rel="stylesheet" type="text/css" />';
		}
		
		$tpl->head .= '
	<script type="text/javascript">
		SyntaxHighlighter.defaults.gutter = false;
		SyntaxHighlighter.all()
	</script>'; 
		
	}


	/**
	 * Compile HTML from wiki content
	 *
	 * @return string $parsed_html
	 * */
	public function compile(){

		$this->__makeSpecialChars();
		$this->__makeCodes();
		$this->__makeLists();

		$this->__makeTables();
		$this->__makePara();
		$this->__makeLinks();
		$this->__makeImages();
		//and reinsert codes:
		$this->__insertCodes();

		if (function_exists('tidy_repair_string')) {
			$this->working = tidy_repair_string($this->working, array(
                            'output-xhtml' => true,
                            'output-encoding' => 'utf8',
                            'enclose-text' => true,
                            'show-body-only' => true,
                            'doctype' => 'strict',
                            'drop-font-tags' => true,
                            'drop-proprietary-attributes' => true,
                            'lower-literals' => true,
                            'quote-ampersand' => true,
                            'indent' => true,
                            'wrap' => 0),
                        'utf8');
		}
		return $this->working;
	}


	private function __makeLinks(){
		$this->working = preg_replace_callback('/\[\[(.*?)\]\]/',create_function('$matches',
                         '$parts = preg_split("/( |\|)/",$matches[1]);
                         if (count($parts)>1) {
                            $link = array_shift($parts);
                            $text = implode(" ", $parts);
                            return \'<a href="\'.$link.\'" title="\'.$text.\'">\'.$text.\'</a>\';
                         } else  return \'<a href="\'.$parts[0].\'" title="\'.$parts[0].\'">\'.$parts[0].\'</a>\';
                         '), $this->working);
	}

	private function __makeImages(){
		$this->working = preg_replace_callback('/\{\{(.*?)\}\}/',create_function('$matches',
                         '$parts = preg_split("/( |\|)/",$matches[1]);
                         if (count($parts)>1) {
                            $link = array_shift($parts);
                            $text = implode(" ", $parts);
                            return \'<img src="\'.$link.\'" alt="\'.$text.\'"/>\';
                         } else  return \'<img src="\'.$parts[0].\'" alt="\'.$parts[0].\'"/>\';
                         '), $this->working);
	}

	private function __makeCodes(){
		preg_match_all('/<code(.*?)>(.*?)<\/code>/s', $this->working,$matches);
		$i=0;
		foreach($matches[0] as $match) {
			$this->working = str_replace($match,'___CODE'.$i.'___', $this->working);
			$this->codes[] = array( 'content' =>$matches[2][$i], 'language' => $matches[1][$i]);
			$i++;
		}
		/*$this->working = preg_replace('/<code\s*(.*?)>/','<pre class="brush: \\1">', $this->working);
		 $this->working = preg_replace('/<\/code>/','</pre>', $this->working);*/
	}

	private function __insertCodes(){
		preg_match_all('/___CODE(\d+)___/', $this->working, $matches);
		$i=0;
		foreach ($matches[0] as $m ){
			$this->working = str_replace($m,'<pre class="brush: '.$this->codes[$i]['language'].'">'."\n".$this->codes[$i]['content'].'</pre>', $this->working);
			$i++;
		}
	}

	private function __makePara(){
		$pass = &$this->working;

		$pass = preg_replace('/\n{2,}/','<p>'."\n",$this->working);

		$pass = preg_replace('/====\s*(.*?)\s*====/', '<h4>\\1</h4><p>'."\n",$pass);
		$pass = preg_replace('/===\s*(.*?)\s*===/', '<h3>\\1</h3><p>'."\n",$pass);
		$pass = preg_replace('/==\s*(.*?)\s*==/', '<h2>\\1</h2><p>'."\n",$pass);

		$pass = preg_replace(';\*\*(.+?)\*\*;', '<strong>\\1</strong>', $pass);
	}


	private function __makeSpecialChars(){
		$content = &$this->working;

		$content = preg_replace('/<</', '&laquo;', $content);
		$content = preg_replace('/>>/', '&raquo;', $content);
		$content = preg_replace('/\(c\)/', '&copy;', $content);
		$content = preg_replace('/\(r\)/', '&reg;', $content);
		$content = preg_replace('/¿/', '&iquest;', $content);

		//money
		$content = preg_replace('/€/', '&euro;', $content);
		$content = preg_replace('/¥/', '&yen;', $content);
		$content = preg_replace('/¢/', '&cent;', $content);
		$content = preg_replace('/£/', '&pound;', $content);

		//arrows
		$content = preg_replace('/-->/', '&rarr;', $content);
		$content = preg_replace('/<--/', '&larr;', $content);
		$content = preg_replace('/<->/', '&harr;', $content);
		$content = preg_replace('/==>/', '&rArr;', $content);
		$content = preg_replace('/<==/', '&lArr;', $content);
		$content = preg_replace('/<=>/', '&hArr;', $content);

		//math
		//$content = preg_replace('/`(.*?)`/', '&\\1;', $content);
		$content = preg_replace('/=</', '&le;', $content);
		$content = preg_replace('/>=/', '&ge;', $content);
		$content = preg_replace('/!=/', '&ne;', $content);
		$content = preg_replace('/~=/', '&cong;', $content);

		$content = str_replace("http://", "_URI_STRING_", $content);
		$content = preg_replace('/\/{2}(.*?)\/{2}/', "<em>\\1</em>", $content);
		$content = str_replace("_URI_STRING_", "http://", $content);
		$content = preg_replace('/_{2}(.*?)_{2}/', "<u>\\1</u>", $content);

	}


	private function __treatLists($block) {
		$lines = explode("\n", $block);
		unset($lines[count($lines)-1]);
		$level = 0;
		$lastlevel = 0;
		$items = array();
		$arLevel=array();
		$maxlevel = 0;
		foreach ($lines as $line) {
			$matches = array ();
			if (preg_match('/(\s+)(\*|-|#)\s*(.*?)$/', $line, $matches)) {
				$listitem = new stdClass();
				if ($matches[2] == "*"){
					$listitem->type = "ul";
				} else {
					$listitem->type = "ol";
				}
				$c = strlen($matches[1]);
				if ($lastlevel<$c){
					if(!isset($arLevel[$c])){
						$arLevel[$c]=++$level;
					}

				} elseif ($lastlevel>$c){
					if(!isset($arLevel[$c])){
						$arLevel[$c]=--$level;
					}
				}
				$listitem->level = isset($arLevel[$c]) ? $arLevel[$c] : -1 ;
				$maxlevel = max($maxlevel, $listitem->level);
				$lastlevel=$listitem->level;
				$listitem->data = $matches[3];
				$items[]=$listitem;
			} //-end list line
		}   //-end foreach line

		//now we can work:
		$level =0;
		$block = "";
		$desc=0;
		$started= false;
		$closedLevel=$maxlevel*2; //to be sure... see later...

		for ($i=0;$i<count($items);$i++){
			$item = &$items[$i];
			if($started && $level == $item->level){
				$block.="</li>\n";
			}
			if($level < $item->level){
				$block.="<".$item->type.">\n";
			}elseif($level > $item->level){
				$j=$i;
				while(isset($items[$j-1]) && $items[$j-1]->level!=$item->level){
					$j--;
					if($items[$j]->level<$closedLevel){
						$block.="</li></".$items[$j]->type.">\n";
						$closedLevel=$items[$j]->level;
					}
				}
				$closedLevel=$maxlevel*2;//but why ???
			}
			$block.="<li>".$item->data;
			$started= true;
			$level=$item->level;
		}

		//close tags
		if($level > 1){
			$j=$i-1; // $i > count($items) cause $i++ on last loop
			while($items[$j-1]->level>=1){
				$j--;
				if($items[$j]->level<$closedLevel){
					$block.="</li></".$items[$j]->type.">\n";
					$closedLevel=$items[$j]->level;
				}
			}
		}

		$block.="</li>\n</".$items[$i-1]->type.">\n\n";

		return $block;
	}


	private function __makeLists(){
		$lines = explode("\n", $this->working);
		$blocks = "";
		$content = "";
		foreach ($lines as $line ) {
			if (preg_match('/^(\s+)(\*|-|#)\s*(.*?)$/', $line, $matches)) {
				$blocks .= $matches[0]."\n";
			}
			else {
				if (strlen($blocks)>0) {
					$content .= $this->__treatLists($blocks)."\n\n";
					$blocks = "";
				}
				$content.=$line."\n";
			}
		}
		if (strlen($blocks)>0) {
			$content .= $this->__treatLists($blocks)."\n\n";
			$blocks = "";
		}
		$this->working = &$content;
	}


	private function __makeTables(){
		$lines = explode("\n", $this->working);
		$table = $content = "";
		foreach ($lines as $line) {
			if (preg_match('/^(\||\^).*(\||\^)$/',trim($line))){
				$table .= $line."\n";
			}
			else {
				if(strlen($table)>0) {
					$this->__transformTable($table);
					$content.=$table;
					$table="";
				}
				$content.=$line."\n";
			}
		}
		if(strlen($table)>0) {
			$this->__transformTable($table);
			$content.=$table;
			$table="";
		}
		$this->working = &$content;
	}


	private function __transformTable(&$code){
		$lines = explode("\n",$code);
		//
		$code = '<table cellspacing="0" cellpadding="0">'."\n";
		foreach($lines as $line){
			if(strlen(trim($line))<1) continue;

			//remove first and last pipe
			/*
			$line = preg_replace('/^\|/',"",$line);
			$line = preg_replace('/\|$/',"",$line);
			*/
			$cells = preg_split('/(\||\^)/s',$line,-1,PREG_SPLIT_DELIM_CAPTURE);
			$code.="\n<tr>\n\t";
			$colspan=1;
			$tomerge=false;
			$cellnum = 0;


			$counter=0;
			$_cells=array();
			foreach($cells as $cell){
				//the first and last elems is always empty
				if($counter==0 || $counter==count($cells)-1){
					$counter++;
				}
				else{
					if($cell=="^" || $cell=="|"){
						//type detected
						$_cell = new stdClass();
						$_cell->type= ($cell=="|") ? "td" : "th";
					}
					else{
						$_cell->data = $cell;
						$_cells[]=$_cell;
					}
					$counter++;
				}
			}

			$cells = $_cells;

			for ($i=0;$i<count($cells);$i++){
				//foreach($cells as $cell){
				$cell = $cells[$i];
				while(isset($cells[$i+1]) && strlen($cells[$i+1]->data)==0) {
					$colspan++;
					$tomerge=true;
					$i++;
				}

				//check alignement
				$alignement = "";
				if(preg_match('/^(\s{2,})/',$cell->data)){
					$alignement="right";
				}
				if(preg_match('/(\s{2,})$/',$cell->data)){
					$alignement="left";
				}

				if(preg_match('/^(\s{2,})/',$cell->data) && $alignement=="left"){
					$alignement="center";
				}

				if(strlen($alignement)){
					$alignement=' style="text-align: '.$alignement.'" ';
				}

				if($tomerge){
					$code.='<'.$cell->type.' colspan="'.$colspan.'"'.$alignement.'>';
					$colspan=1;
					$tomerge=false;
				}
				else{
					$code.='<'.$cell->type.$alignement.'>';
				}
				if(strlen($cell->data)>0) {
					if($cell->data==" ")
					$code.='&nbsp;</'.$cell->type.'>';
					else
					$code.=$cell->data.'</'.$cell->type.'>';
				}
				$cellnum++;
			}
			$code.="</tr>\n";
		}
		$code .= "</table>\n";
	}

}

